package daobeauty.interceptor.invoke.impl;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Query;
import org.hibernate.SessionFactory;

import daobeauty.interceptor.invoke.AbstractCurrentSessionInvokeStrategy;
import daobeauty.interceptor.invoke.ArgumentMask;
import daobeauty.interceptor.invoke.fill.ArrayQueryFill;
import daobeauty.interceptor.invoke.fill.CollectionQueryFill;
import daobeauty.interceptor.invoke.fill.EmptyFill;
import daobeauty.interceptor.invoke.fill.MapQueryFill;
import daobeauty.interceptor.invoke.fill.PlainQueryFill;
import daobeauty.interceptor.invoke.fill.ParameterFillStrategy;
import daobeauty.interceptor.returntype.executor.query.QueryExecutor;
import daobeauty.util.MethodSignatureUtils;

/**
 * Obtains a named query, that has the same name as the used method.
 * E. g. the named query "myNamedQuery" can be mapped through the method myNamedQuery
 * @author M. Serht Cinar
 *
 */
public class NamedQueryDelegateInvoke extends AbstractCurrentSessionInvokeStrategy {
	private static final Log LOG = LogFactory.getLog(NamedQueryDelegateInvoke.class);
	
	private final QueryExecutor executor;
	private final ArgumentMask argumentMask;
	private final ParameterFillStrategy fillStrategy;
	
	public NamedQueryDelegateInvoke(Method method, SessionFactory sessionFactory, Class domainClass) {
		super(sessionFactory, domainClass);
		final Class returnType = method.getReturnType();
		if (returnType==null){
			throw new IllegalArgumentException("method "+MethodSignatureUtils.toMethodSignature(method)+" must have return type");
		}
		if (GetNamedQueryInvoke.getNamedQuery(method.getName(), domainClass, getCurrentSession())==null){
			throw new IllegalArgumentException("method "+MethodSignatureUtils.toMethodSignature(method)+" can't be mapped to a named query: no named query \""+method.getName()+"\" found.");
		}
		
		executor = new QueryExecutor(method.getReturnType());
		argumentMask = new ArgumentMask(method);
		final Class [] parameterTypes = method.getParameterTypes();
		// case public List myNamedQuery();
		if (parameterTypes==null || parameterTypes.length<1){
			fillStrategy = EmptyFill.INSTANCE;
		}
		// case public List myNamedQuery(Map valuesByName);
		else if (parameterTypes[0]!=null && Map.class.isAssignableFrom(parameterTypes[0])){
			fillStrategy = new MapQueryFill();
		}
		// case public List myNamedQuery(Collection values);
		else if (parameterTypes[0]!=null && Collection.class.isAssignableFrom(parameterTypes[0])){
			fillStrategy = new CollectionQueryFill();
		}
		// case public List myNamedQuery(Object [] values);
		else if (parameterTypes[0]!=null && Object[].class.isAssignableFrom(parameterTypes[0])){
			fillStrategy = new ArrayQueryFill();
		}
		// cases public List myNamedQuery(Object parameter);
		// cases public List myNamedQuery(Object parameter1, Object parameter2, ...);
		else{
			fillStrategy = new PlainQueryFill();
		}
		if (LOG.isInfoEnabled()){
			LOG.info("created namedQueryDelegate strategy for query "+method.getName()+(argumentMask.hasLimiting()?" with limiting":"")+(argumentMask.hasSorting()?" with sorting":"")+" for method "+MethodSignatureUtils.toMethodSignature(method));
		}
	}
	
	public Object invoke(String methodName, Object[] args) throws Exception {
		final Query namedQuery = GetNamedQueryInvoke.getNamedQuery(methodName, getDomainClass(), getCurrentSession());
		fillStrategy.fillParameters(namedQuery, argumentMask.getFiltered(args));
		return executor.execute(namedQuery, argumentMask.getLimiting(args));
	}
}
